-
-
Notifications
You must be signed in to change notification settings - Fork 736
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix/last seen at by environment #4939
Conversation
The latest updates on your projects. Learn more about Vercel for Git ↗︎
|
import { LastSeenService } from './last-seen-service'; | ||
import LastSeenStore from './last-seen-store'; | ||
|
||
export const createLastSeenService = ( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use composite root pattern for LastSeenService
import { IFeatureLastSeenResults } from './last-seen-read-model'; | ||
|
||
export class LastSeenMapper { | ||
mapToFeatures( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Map results of reading into environments of incoming features to maintain todays data structure, might change in follow up iteration
}; | ||
}; | ||
} | ||
export class LastSeenAtReadModel implements ILastSeenReadModel { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
retrieve last seen at results per environment for an array of features
if (this.config.flagResolver.isEnabled('useLastSeenRefactor')) { | ||
await this.lastSeenStore.setLastSeen(lastSeenToggles); | ||
} else { | ||
await this.featureToggleStore.setLastSeen(lastSeenToggles); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
maintain previous implementation
async setLastSeen(data: LastSeenInput[]): Promise<void> { | ||
const now = new Date(); | ||
|
||
try { | ||
const inserts = data.map((item) => { | ||
return { | ||
feature_name: item.featureName, | ||
environment: item.environment, | ||
last_seen_at: now, | ||
}; | ||
}); | ||
|
||
const batchSize = 1000; | ||
|
||
await this.db.transaction(async (trx) => { | ||
for (let i = 0; i < inserts.length; i += batchSize) { | ||
const batch = inserts.slice(i, i + batchSize); | ||
// Knex optimizes multi row insert when given an array: | ||
// https://knexjs.org/guide/query-builder.html#insert | ||
await trx(TABLE) | ||
.insert(batch) | ||
.onConflict(['feature_name', 'environment']) | ||
.merge(); | ||
} | ||
}); | ||
} catch (err) { | ||
this.logger.error('Could not update lastSeen, error: ', err); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Batch insert last seen in batches of 1000
src/lib/services/project-service.ts
Outdated
if (this.flagResolver.isEnabled('useLastSeenRefactor')) { | ||
const mapper = new LastSeenMapper(); | ||
|
||
const featureNames = features.map((feature) => feature.name); | ||
const lastSeenAtPerEnvironment = | ||
await this.lastSeenReadModel.getForFeature(featureNames); | ||
|
||
console.log(decoratedFeatures, lastSeenAtPerEnvironment); | ||
|
||
decoratedFeatures = mapper.mapToFeatures( | ||
decoratedFeatures, | ||
lastSeenAtPerEnvironment, | ||
this.logger, | ||
); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Decorate features with lastSeenData
exports.up = function (db, callback) { | ||
db.runSql( | ||
` | ||
CREATE TABLE last_seen_at_metrics ( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
new table, foreign key on environments
Looks good. Only suggestion is we could add the batch size as a variant in the flag. Design decisions make sense to me |
|
||
const batchSize = 1000; | ||
|
||
await this.db.transaction(async (trx) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd do the transaction around the service (in the controller) as we do for all new code.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since it's a non critical data I'm wondering if having transactions even makes sense here. I'd consider sacrificing correctness for performance.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agree, done
this.featureToggleStore = featureToggleStore; | ||
this.logger = config.getLogger( | ||
'/services/client-metrics/last-seen-service.ts', | ||
); | ||
this.config = config; | ||
|
||
this.timers.push( | ||
setInterval(() => this.store(), lastSeenInterval).unref(), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
shouldn't this be part of the scheduler?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I will look into separating this in a follow-up
b5c94c9
to
9a961a7
Compare
}; | ||
|
||
exports.down = function (db, callback) { | ||
callback(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
missing down migration
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd run clinic.js with both last seen variants to compare the performance characteristics
This is a draft of the new architecture for last seen at. Apologies for size, but I initially explored triggers and couldn't merge that before it was done. I changed my approach mid-way and now I'd like to verify the direction and merge before doing the remaining endpoints.
Discussion
Today there are 5 pages accessing this information:
The last seen at property is in it's current form being added to the data structure of the environments property that exists on the feature data structure on some of these views. Since some of the views don't have it, its implementation varies across views. I have chosen not to change the data structure in this iteration, which has influenced the design decisions of reading the data.
It might it would be better if the data was added as an additional field directly on the feature data structure itself, only for the read operations in the admin panel:
Other discussion points:
Follow up actions